// StreamManipulator.cs
//
// Copyright (C) 2001 Mike Krueger
//
// This file was translated from java, it was part of the GNU Classpath
// Copyright (C) 2001 Free Software Foundation, Inc.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
// 
// As a special exception, the copyright holders of this library give you
// permission to link this library with independent modules to produce an
// executable, regardless of the license terms of these independent
// modules, and to copy and distribute the resulting executable under
// terms of your choice, provided that you also meet, for each linked
// independent module, the terms and conditions of the license of that
// module.  An independent module is a module which is not derived from
// or based on this library.  If you modify this library, you may extend
// this exception to your version of the library, but you are not
// obligated to do so.  If you do not wish to do so, delete this
// exception statement from your version.

using System;

namespace PdfSharp.SharpZipLib.Zip.Compression.Streams
{

  /// <summary>
  /// This class allows us to retrieve a specified number of bits from
  /// the input buffer, as well as copy big byte blocks.
  ///
  /// It uses an int buffer to store up to 31 bits for direct
  /// manipulation.  This guarantees that we can get at least 16 bits,
  /// but we only need at most 15, so this is all safe.
  ///
  /// There are some optimizations in this class, for example, you must
  /// never peek more than 8 bits more than needed, and you must first
  /// peek bits before you may drop them.  This is not a general purpose
  /// class but optimized for the behaviour of the Inflater.
  ///
  /// Authors of the original java version: John Leuner, Jochen Hoenicke
  /// </summary>
  internal class StreamManipulator
  {
    private byte[] window;
    private int window_start = 0;
    private int window_end = 0;

    private uint buffer = 0;
    private int bits_in_buffer = 0;

    /// <summary>
    /// Get the next n bits but don't increase input pointer.  n must be
    /// less or equal 16 and if this call succeeds, you must drop
    /// at least n - 8 bits in the next call.
    /// </summary>
    /// <returns>
    /// the value of the bits, or -1 if not enough bits available.  */
    /// </returns>
    public int PeekBits(int n)
    {
      if (bits_in_buffer < n)
      {
        if (window_start == window_end)
        {
          return -1; // ok
        }
        buffer |= (uint)((window[window_start++] & 0xff |
                         (window[window_start++] & 0xff) << 8) << bits_in_buffer);
        bits_in_buffer += 16;
      }
      return (int)(buffer & ((1 << n) - 1));
    }

    /// <summary>
    /// Drops the next n bits from the input.  You should have called PeekBits
    /// with a bigger or equal n before, to make sure that enough bits are in
    /// the bit buffer.
    /// </summary>
    public void DropBits(int n)
    {
      buffer >>= n;
      bits_in_buffer -= n;
    }

    /// <summary>
    /// Gets the next n bits and increases input pointer.  This is equivalent
    /// to PeekBits followed by dropBits, except for correct error handling.
    /// </summary>
    /// <returns>
    /// the value of the bits, or -1 if not enough bits available.
    /// </returns>
    public int GetBits(int n)
    {
      int bits = PeekBits(n);
      if (bits >= 0)
      {
        DropBits(n);
      }
      return bits;
    }

    /// <summary>
    /// Gets the number of bits available in the bit buffer.  This must be
    /// only called when a previous PeekBits() returned -1.
    /// </summary>
    /// <returns>
    /// the number of bits available.
    /// </returns>
    public int AvailableBits
    {
      get
      {
        return bits_in_buffer;
      }
    }

    /// <summary>
    /// Gets the number of bytes available.
    /// </summary>
    /// <returns>
    /// The number of bytes available.
    /// </returns>
    public int AvailableBytes
    {
      get
      {
        return window_end - window_start + (bits_in_buffer >> 3);
      }
    }

    /// <summary>
    /// Skips to the next byte boundary.
    /// </summary>
    public void SkipToByteBoundary()
    {
      buffer >>= (bits_in_buffer & 7);
      bits_in_buffer &= ~7;
    }

    /// <summary>
    /// Returns true when SetInput can be called
    /// </summary>
    public bool IsNeedingInput
    {
      get
      {
        return window_start == window_end;
      }
    }

    /// <summary>
    /// Copies length bytes from input buffer to output buffer starting
    /// at output[offset].  You have to make sure, that the buffer is
    /// byte aligned.  If not enough bytes are available, copies fewer
    /// bytes.
    /// </summary>
    /// <param name="output">
    /// The buffer to copy bytes to.
    /// </param>
    /// <param name="offset">
    /// The offset in the buffer at which copying starts
    /// </param>
    /// <param name="length">
    /// The length to copy, 0 is allowed.
    /// </param>
    /// <returns>
    /// The number of bytes copied, 0 if no bytes were available.
    /// </returns>
    /// <exception cref="ArgumentOutOfRangeException">
    /// Length is less than zero
    /// </exception>
    /// <exception cref="InvalidOperationException">
    /// Bit buffer isnt byte aligned
    /// </exception>
    public int CopyBytes(byte[] output, int offset, int length)
    {
      if (length < 0)
      {
        throw new ArgumentOutOfRangeException("length");
      }
      if ((bits_in_buffer & 7) != 0)
      {
        /* bits_in_buffer may only be 0 or a multiple of 8 */
        throw new InvalidOperationException("Bit buffer is not byte aligned!");
      }

      int count = 0;
      while (bits_in_buffer > 0 && length > 0)
      {
        output[offset++] = (byte)buffer;
        buffer >>= 8;
        bits_in_buffer -= 8;
        length--;
        count++;
      }

      if (length == 0)
      {
        return count;
      }

      int avail = window_end - window_start;
      if (length > avail)
      {
        length = avail;
      }
      System.Array.Copy(window, window_start, output, offset, length);
      window_start += length;

      if (((window_start - window_end) & 1) != 0)
      {
        /* We always want an even number of bytes in input, see peekBits */
        buffer = (uint)(window[window_start++] & 0xff);
        bits_in_buffer = 8;
      }
      return count + length;
    }

    /// <summary>
    /// Constructs a default StreamManipulator with all buffers empty
    /// </summary>
    public StreamManipulator()
    {
    }


    /// <summary>
    /// resets state and empties internal buffers
    /// </summary>
    public void Reset()
    {
      buffer = (uint)(window_start = window_end = bits_in_buffer = 0);
    }

    /// <summary>
    /// Add more input for consumption.
    /// Only call when IsNeedingInput returns true
    /// </summary>
    /// <param name="buf">data to be input</param>
    /// <param name="off">offset of first byte of input</param>
    /// <param name="len">length of input</param>
    public void SetInput(byte[] buf, int off, int len)
    {
      if (window_start < window_end)
      {
        throw new InvalidOperationException("Old input was not completely processed");
      }

      int end = off + len;

      /* We want to throw an ArrayIndexOutOfBoundsException early.  The
      * check is very tricky: it also handles integer wrap around.
      */
      if (0 > off || off > end || end > buf.Length)
      {
        throw new ArgumentOutOfRangeException();
      }

      if ((len & 1) != 0)
      {
        /* We always want an even number of bytes in input, see peekBits */
        buffer |= (uint)((buf[off++] & 0xff) << bits_in_buffer);
        bits_in_buffer += 8;
      }

      window = buf;
      window_start = off;
      window_end = end;
    }
  }
}
